/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.merak.core.persistence.jdbc;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;

import javax.naming.NamingException;

import org.merak.core.Application;
import org.merak.core.model.ProgrammaticException;
import org.merak.core.persistence.jdbc.settings.ConnectionSetting;

public class XConnection implements Connection {

	//~ Attributes /////////////////////////////////////////////////////////////
	//**************************************************************************
	private Connection        conn       = null;
	private ConnectionSetting settings   = null;
	private boolean           connecting = false;

	//~ Constructors ///////////////////////////////////////////////////////////
	//**************************************************************************
	public XConnection(ConnectionSetting settings) {
		this.settings = settings;
	}

	//~ Methods ////////////////////////////////////////////////////////////////
	//**************************************************************************
	/** Used to protect opening of the wrapped connection. While connecting,
	 *  connection is set as "connecting" or "not connecting". These states
	 *  will help the Job responsible by restoring the connection, avoiding new
	 *  Jobs of being fired while a current one is already running.
	 *  @see RecoveryManager */
	protected synchronized void connect()
		throws SQLException, ClassNotFoundException, NamingException
	{

		try {
			this.connecting = true;
			this.conn       = SQLConnectionFactory.connectTo( this.getSettings() );
			this.connecting = false;
		}
		catch (SQLException e) {
			Application.database.getLogger().error(
				this.settings + ": Connection failed because of " + e
			);
			throw e;
		}

	}
	//**************************************************************************
	/** Used for protected access of wrapped connection attribute, it returns the
	 *  connection if already open, or will open if closed. If it fails to open,
	 *  it will fire a Job that keeps trying to open the connection */
	protected synchronized Connection getWrappedConnection() throws SQLException {

		/* If this connection is recovering, don't try reconnecting, but throw
		 * an exception about it. */
		if (this.connecting) {
			throw new SQLException(
				this.settings  + ": Connection is down. The system is " +
				"currently trying to restore this connection, please wait."
			);
		}

		// 1. Return connection if not closed. Else, try opening it.
		if ( this.isClosed() ) {
			try {
				this.connect();
			}

			// 2. If opening the connection fails, try restoring it
			catch (SQLException e) {
				/* Ensures the SQLException is due to a closed connection.
				 * If connection restores, ignore the exception and continue. */
				if (this.isClosed()) {
					RecoveryManager.getInstance().restore(this,e);
					if (!this.isClosed()) {
						return this.conn;
					}
				}
				// Throw the exception if reconnection is really impossible
				throw e;
			}

			// 3. Detect if connection failed due to bug
			catch (Exception e) {throw new ProgrammaticException(
				this.settings + ": Bugs are preventing connections.",e
			);}
		}
		return this.conn;

	}
	//**************************************************************************
	/** Returns the settings used to create this connection
	 * @see ApplicationData/Databases.xml */
	public ConnectionSetting getSettings() {
		return this.settings;
	}
	//**************************************************************************
	@Override
	public Statement createStatement() throws SQLException {
		return new XStatement(this);
	}
	//**************************************************************************
	@Override
	public PreparedStatement prepareStatement(String template) throws SQLException {
		return new XPreparedStatement(this, template);
	}
	//**************************************************************************
	@Override
	public void close() {

		try {
			if (this.conn != null) {
				this.conn.close();
			}
		}
		catch(Exception ex) {}

	}
	//**************************************************************************
	@Override
	public void clearWarnings() throws SQLException {
		this.getWrappedConnection().clearWarnings();
	}

	//**************************************************************************
	@Override
	public void commit() throws SQLException {
		this.getWrappedConnection().commit();
	}

	//**************************************************************************
	@Override
	public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
		return this.getWrappedConnection().createArrayOf(typeName, elements);
	}
	//**************************************************************************
	@Override
	public Blob createBlob() throws SQLException {
		return this.getWrappedConnection().createBlob();
	}
	//**************************************************************************
	@Override
	public Clob createClob() throws SQLException {
		return this.getWrappedConnection().createClob();
	}
	//**************************************************************************
	@Override
	public NClob createNClob() throws SQLException {
		return this.getWrappedConnection().createNClob();
	}
	//**************************************************************************
	@Override
	public SQLXML createSQLXML() throws SQLException {
		return this.getWrappedConnection().createSQLXML();
	}
	//**************************************************************************
	@Override
	public Statement createStatement(int resultSetType,int resultSetConcurrency, int resultSetHoldability)
		throws SQLException
	{
		return this.getWrappedConnection().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
	}
	//**************************************************************************
	@Override
	public Statement createStatement(int resultSetType, int resultSetConcurrency)
		throws SQLException
	{
		return this.getWrappedConnection().createStatement(resultSetType, resultSetConcurrency);
	}
	//**************************************************************************
	@Override
	public Struct createStruct(String typeName, Object[] attributes)
		throws SQLException
	{
		return this.getWrappedConnection().createStruct(typeName, attributes);
	}
	//**************************************************************************
	@Override
	public boolean getAutoCommit() throws SQLException {
		return this.getWrappedConnection().getAutoCommit();
	}
	//**************************************************************************
	@Override
	public String getCatalog() throws SQLException {
		return this.getWrappedConnection().getCatalog();
	}
	//**************************************************************************
	@Override
	public Properties getClientInfo() throws SQLException {
		return null;
	}
	//**************************************************************************
	@Override
	public String getClientInfo(String name) throws SQLException {
		return this.getWrappedConnection().getClientInfo(name);
	}
	//**************************************************************************
	@Override
	public int getHoldability() throws SQLException {
		return this.getWrappedConnection().getHoldability();
	}
	//**************************************************************************
	@Override
	public DatabaseMetaData getMetaData() throws SQLException {
		return this.getWrappedConnection().getMetaData();
	}
	//**************************************************************************
	@Override
	public int getTransactionIsolation() throws SQLException {
		return this.getWrappedConnection().getTransactionIsolation();
	}
	//**************************************************************************
	@Override
	public Map<String, Class<?>> getTypeMap() throws SQLException {
		return this.getWrappedConnection().getTypeMap();
	}
	//**************************************************************************
	@Override
	public SQLWarning getWarnings() throws SQLException {
		return this.getWrappedConnection().getWarnings();
	}
	//**************************************************************************
	@Override
	public boolean isClosed() throws SQLException {
		return this.conn == null || this.conn.isClosed();
	}
	//**************************************************************************
	@Override
	public boolean isReadOnly() throws SQLException {
		return this.getWrappedConnection().isReadOnly();
	}
	//**************************************************************************
	@Override
	public boolean isValid(int timeout) throws SQLException {
		return this.getWrappedConnection().isValid(timeout);
	}
	//**************************************************************************
	@Override
	public String nativeSQL(String sql) throws SQLException {
		return this.getWrappedConnection().nativeSQL(sql);
	}
	//**************************************************************************
	@Override
	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency, int resultSetHoldability)
			throws SQLException {

		return this.getWrappedConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
	}
	//**************************************************************************
	@Override
	public CallableStatement prepareCall(String sql, int resultSetType,
			int resultSetConcurrency) throws SQLException {

		return this.getWrappedConnection().prepareCall(sql, resultSetType, resultSetConcurrency);

	}
	//**************************************************************************
	@Override
	public CallableStatement prepareCall(String sql) throws SQLException {
		return this.getWrappedConnection().prepareCall(sql);
	}
	//**************************************************************************
	@Override
	public PreparedStatement prepareStatement(String sql, int resultSetType,int resultSetConcurrency, int resultSetHoldability)
		throws SQLException
	{
		return this.getWrappedConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
	}

	//**************************************************************************
	@Override
	public PreparedStatement prepareStatement(String sql, int resultSetType,int resultSetConcurrency)
		throws SQLException
	{
		return this.getWrappedConnection().prepareStatement(sql, resultSetType, resultSetConcurrency);
	}
	//**************************************************************************
	@Override
	public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
		throws SQLException
	{
		return this.getWrappedConnection().prepareStatement(sql, autoGeneratedKeys);
	}
	//**************************************************************************
	@Override
	public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
		throws SQLException
	{
		return this.getWrappedConnection().prepareStatement(sql, columnIndexes);
	}
	//**************************************************************************
	@Override
	public PreparedStatement prepareStatement(String sql, String[] columnNames)
		throws SQLException
	{
		return this.getWrappedConnection().prepareStatement(sql, columnNames);
	}
	//**************************************************************************
	@Override
	public void releaseSavepoint(Savepoint savepoint) throws SQLException {
		this.getWrappedConnection().releaseSavepoint(savepoint);
	}
	//**************************************************************************
	@Override
	public void rollback() throws SQLException {
		this.getWrappedConnection().rollback();
	}
	//**************************************************************************
	@Override
	public void rollback(Savepoint savepoint) throws SQLException {
		this.getWrappedConnection().rollback(savepoint);
	}
	//**************************************************************************
	@Override
	public void setAutoCommit(boolean autoCommit) throws SQLException {
		this.getWrappedConnection().setAutoCommit(autoCommit);
	}
	//**************************************************************************
	@Override
	public void setCatalog(String catalog) throws SQLException {
		this.getWrappedConnection().setCatalog(catalog);
	}
	//**************************************************************************
	@Override
	public void setClientInfo(Properties properties) throws SQLClientInfoException	{

		try {
			this.getWrappedConnection().setClientInfo(properties);
		}
		catch (SQLException e) {
			throw new RuntimeException(e);
		}

	}
	//**************************************************************************
	@Override
	public void setClientInfo(String name, String value) throws SQLClientInfoException {

		try {
			this.getWrappedConnection().setClientInfo(name, value);
		}
		catch (SQLException e) {
			throw new RuntimeException(e);
		}

	}
	//**************************************************************************
	@Override
	public void setHoldability(int holdability) throws SQLException {
		this.getWrappedConnection().setHoldability(holdability);
	}
	//**************************************************************************
	@Override
	public void setReadOnly(boolean readOnly) throws SQLException {
		this.getWrappedConnection().setReadOnly(readOnly);
	}
	//**************************************************************************
	@Override
	public Savepoint setSavepoint() throws SQLException {
		return this.getWrappedConnection().setSavepoint();
	}
	//**************************************************************************
	@Override
	public Savepoint setSavepoint(String name) throws SQLException {
		return this.getWrappedConnection().setSavepoint(name);
	}
	//**************************************************************************
	@Override
	public void setTransactionIsolation(int level) throws SQLException {
		this.getWrappedConnection().setTransactionIsolation(level);
	}
	//**************************************************************************
	@Override
	public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
		this.getWrappedConnection().setTypeMap(map);
	}
	//**************************************************************************
	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return this.getWrappedConnection().isWrapperFor(iface);
	}
	//**************************************************************************
	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		return this.getWrappedConnection().unwrap(iface);
	}
	//**************************************************************************
}

